package com.interactive.internal.ant;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Vector;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;

/** Ant task to generate main test class, which runs all found tests.
 *
 * Note: This is a modified version of the ant task defined by Thomas Nazar @
 * http://www.ii.uni.wroc.pl/~nthx/java/ant.html#ant-tasks.  It has been changed
 * to meet the needs of the INT build system and moved to the package
 * com.interactive.internal.ant along with other custom ant tasks used in the
 * INT build system.  This task is not intended for redistribution.
 *
 * <p>Use:
 *  <pre>
 *   &lt;target name="tests_generated_master" depends="read_params"&gt;
 *       &lt;taskdef name="master_test_gen" classname="com.interactive.internal.ant.tasks.TestDifferentator"/&gt;
 *       &lt;master_test_gen
 *         masterTestFile="src/main/nthx/ilybra/tests/MasterTest.java"
 *         masterTestClass="MasterTest"
 *         masterTestPackage="nthx.ilybra.tests"&gt;
 *               &lt;fileset dir="${src.main.dir}"&gt;
 *                   &lt;include name="**<s>/</s>*Test.java"/&gt;
 *                   &lt;exclude name="**<s>/</s>*MasterTest.java"/&gt;
 *                   &lt;exclude name="**<s>/</s>Log4j*.java"/&gt;
 *               &lt;/fileset&gt;
 *       &lt;/master_test_gen&gt;
 *   &lt;/target&gt;
 *  </pre>
 *
 *  @author nthx@irc.pl
 *
 */
public class SuiteGenerator
    extends Task
{

  protected File masterTestFile = null;
  protected String masterTestClass = null;
  protected String masterTestPackage = null;
  private String testRegEx = ".*";

  protected Vector filesets = new Vector();
  private File srcDir;

  public SuiteGenerator()
  {
  }

  public void addFileset(FileSet set)
  {
    filesets.addElement(set);
  }

  public String getTestRegEx()
  {
    return testRegEx;
  }

  public void setTestRegEx(String regEx)
  {
    testRegEx = regEx;
  }

  //-----------main-------------
  public void execute()
      throws BuildException
  {
    validateAttributes();

    PrintWriter printWriter = null;

    try
    {
      // deal with the filesets
      //package some.package
      File testFile = getMasterTestFile();
      testFile.mkdirs();
      if (testFile.exists() && !testFile.delete())
        throw new BuildException("Could not recreate file: " + testFile);
      if (!testFile.createNewFile())
        throw new BuildException("Could not create file: " + testFile);
      printWriter = new PrintWriter(new FileWriter(testFile));

      log("Creating test file: " + testFile, Project.MSG_VERBOSE);
      writeMasterTestHeaderFirst(printWriter);
      writeMasterTestHeaderThird(printWriter);

      // Write unit tests

      if (filesets.size() < 1)
        throw new BuildException("You must define at least one fileset of tests to run!");
      FileSet fs = (FileSet) filesets.elementAt(0);
      DirectoryScanner ds = fs.getDirectoryScanner(getProject());
      String[] srcFiles = ds.getIncludedFiles();
      writeTests(printWriter, srcFiles);


      for (int i = 1; i < filesets.size(); i++)
      {
        fs = (FileSet) filesets.elementAt(i);
        ds = fs.getDirectoryScanner(getProject());

        srcFiles = ds.getIncludedFiles();
        writeClassForNames(printWriter, srcFiles);
      }
      writeMasterTestFooter(printWriter);
      log("Created test file: " + testFile, Project.MSG_VERBOSE);
    }
    catch (IOException e)
    {
      throw new BuildException(e);
    }
    finally
    {
      if (printWriter != null)
        printWriter.close();
      masterTestFile = null;
    }
  }

  protected void validateAttributes()
      throws BuildException
  {
    if (getMasterTestFile() == null)
    {
      throw new BuildException("No input file!");
    }
  }

  private void writeTests(PrintWriter writer, String[] files)
  {
    for (int i = 0; i < files.length; i++)
    {
        log("Adding file to test suite: " + files[i], Project.MSG_VERBOSE);
        println(writer, "        result.addTest(new TestSuite("
                + getClass(files[i])
                + ".class));");
    }
  }

  private void writeClassForNames(PrintWriter writer, String[] files)
  {
    for (int i = 0; i < files.length; i++)
    {
      log("Adding production class to test suite: " + files[i], Project.MSG_VERBOSE);
      println(writer, "        Class.forName(\""
              + getClass(files[i])
              + "\");");
    }
  }


  boolean isTest(String file)
  {
    return getClassName(file).matches(getTestRegEx());
  }

  String getClass(String file)
  {
    int dotJavaIndex = file.lastIndexOf('.');
    String className = file.substring(0, dotJavaIndex);
    className = className.replace(System.getProperty("file.separator").charAt(0),
                                  '.');
    return className;
  }

  String getClassName(String file)
  {
    String className = getClass(file);
    int dotJavaIndex = className.lastIndexOf('.');
    if (dotJavaIndex == -1)
    {
      return className;
    }
    return className.substring(dotJavaIndex + 1);
  }

  private void writeMasterTestHeaderFirst(PrintWriter writer)
  {
    println(writer, "package " + getMasterTestPackage() + ";");
    println(writer, "import junit.framework.Test;");
    println(writer, "import junit.framework.TestCase;");
    println(writer, "import junit.framework.TestSuite;");
  }

  private void writeMasterTestHeaderThird(PrintWriter writer)
  {
    println(writer, "");
    println(writer, "/**");
    println(writer,
            " *  @author Generated by INT's ant task: " + (new Date()).toString());
    println(writer, " */");
    println(writer, "public class " + getTestClassName());
    println(writer, "       extends TestCase");
    println(writer, "{");
    println(writer);
    println(writer, "    public " + getTestClassName() + "(String arg0)");
    println(writer, "    {");
    println(writer, "        super(arg0);");
    println(writer, "    }");
    println(writer);
    println(writer,
            "    public static void main(String[] args) throws Exception");
    println(writer, "    {");
    println(writer, "        junit.textui.TestRunner.run(suite());");
    println(writer, "        System.exit(0);");
    println(writer, "    }");
    println(writer);
    println(writer, "    public static Test suite() throws Exception");
    println(writer, "    {");
    println(writer, "        TestSuite result = new TestSuite();");
    println(writer);
  }

  private void writeMasterTestFooter(PrintWriter writer)
  {
    println(writer);
    println(writer, "        return result; ");
    println(writer);
    println(writer, "    }");
    println(writer, "}");
  }

  private void println(PrintWriter writer, String line)
  {
    writer.print(line + "\n");
  }

  private void println(PrintWriter writer)
  {
    println(writer, "");
  }

  File getMasterTestFile()
  {
    String filePath = masterTestClass.replace('.', '/') + ".java";
    return new File(srcDir.getAbsolutePath() + "/" + filePath);
  }

  String getMasterTestPackage()
  {
    return masterTestClass.substring(0, masterTestClass.lastIndexOf('.'));
  }

  //---getters and setters -----------------------
  public Vector getFilesets()
  {
    return filesets;
  }

  public void setFilesets(Vector filesets)
  {
    this.filesets = filesets;
  }

  public void setTestSrcdir(File testSrcDir)
  {
    srcDir = testSrcDir;
  }

  public File getTestSrcDir()
  {
    return srcDir;
  }

  public String getTestClassName()
  {
    return masterTestClass.substring(masterTestClass.lastIndexOf('.') + 1);
  }

  public void setMasterTestClass(String masterTestClass)
  {
    if (masterTestClass.endsWith(".class"))
      masterTestClass = masterTestClass.substring(0, masterTestClass.lastIndexOf(".class"));
    this.masterTestClass = masterTestClass;
  }

}
